# 
# ioman_base.py
# 
# Copyright (c) 2005-2011 by Kenjiro Taura. All rights reserved.
#
# THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY 
# EXPRESSED OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
# 
# Permission is hereby granted to use or copy this program
# for any purpose,  provided the above notices are retained on all 
# copies. Permission to modify the code and to distribute modified
# code is granted, provided the above notices are retained, and
# a notice that the code was modified is included with the above
# copyright notice.
#

import sys

import pp
import gxp_logger

LOG = gxp_logger.LOG
dbg = gxp_logger.dbg

# 
# --------- interface ---------
# 

class pipe_type:
    """
    a class defining constants for various pipes
    """
    REGULAR = "REGULAR"
    SOCK_PAIR = "SOCK_PAIR"
    PTY = "PTY"

class event_read:
    """
    a class representing an event that data is read.
    """
    def __init__(self, ch, data, eof, err):
        """
        construct an event_read object (only called by 
        the system).
        
        ch   : a channel you received data from. 
               an instance of one of subclasses of the chan class.
        data : payload (string). None if you got an error.
        eof  : 0 or 1 (meaning your are end of file)
        err  : None if there is no error. otherwise an exception object.
        """
        self.ch = ch
        self.data = data
        self.eof = eof
        self.err = err
    def __str__(self):
        return ("event_read(ch=%s, data=%s, eof=%d, err=%s)" 
                % (self.ch, pp.pp(self.data), self.eof, pp.pp(self.err)))

class event_accept:
    """
    a class representing an event that you have accepted 
    a connection request.
    """
    def __init__(self, ch, new_ch, err):
        """
        ch     : a channel you accepted the connection from.
        new_ch : a new channel (representing the socket connected
                 to the client just connected). None if you got
                 an error.
        err    : None if there is no error. otherwise an exception object.
        """
        self.ch = ch
        self.new_ch = new_ch
        self.err = err
    def __str__(self):
        return ("event_accept(ch=%s, new_ch=%s, err=%s)" 
                % (self.ch, self.new_ch, pp.pp(self.err)))

class event_write:
    """
    a class representing an event that a write you have 
    requested before has just locally completed.
    """
    def __init__(self, ch, pending_bytes, pending_close, err):
        """
        ch     : a channel you completed your write request for
        pending_bytes : an integer indicating how many more bytes
               are waiting to be written to this channel 
               (this is positive when you queued multiple write 
               requests).
        pending_close : 1 if a close request has been issued on
               this channel, waiting for all write requests to be
               completed.
        err  : None if there is no error. otherwise an exception object.
        """
        self.ch = ch
        self.pending_bytes = pending_bytes
        self.pending_close = pending_close
        self.err = err
    def __str__(self):
        return ("event_write(ch=%s, pending_bytes=%d, pending_close=%d, err=%s)" 
                % (self.ch, self.pending_bytes, 
                   self.pending_close, pp.pp(self.err)))

class event_death:
    """
    a class representing an event that one of child processes
    has finished
    """
    def __init__(self, pid, status):
        """
        pid : process id of the child process that has just
              terminated
        status : status (obtained by waitpid on Unix or 
              GetExitCodeProcess on Windows)
        """
        self.pid = pid
        self.status = status
        self.err = None
    def __str__(self):
        return ("event_death(pid=%d, status=%d)" 
                % (self.pid, self.status))

class event_win_ctrlc:
    """
    a special event generated by ctrl-c on windows.
    never created on Unix. (On Unix, SIGINT is handled 
    internally and does not generate any event)
    """
    def __init__(self, ch):
        """
        ch : a chan_win_ctrlc object, a special object
        which is created only to catch ctrl-c event
        err  : None if there is no error. otherwise an exception object.
        """
        self.ch = ch
        self.err = None

class should_be_implemented_in_subclass(Exception):
    """
    a special class to catch an error in whic
    an unimplemented methods is called
    """
    pass

class ioman_ex_bug(Exception):
    """
    a special class to catch an internal bug
    """
    pass

#
# Channel Interfaces
# 
# chan ---- chan_read
#        +- chan_write
#        +- chan_accept
#        +- chan_child_proc
#

class chan:
    """
    the base class representing a communication channel.
    Unix sockets, Unix file descriptors (end of pipes), 
    Windows sockets, Windows handles (end of pipes), etc.
    users usually do not have to create channel objects
    by themselves; they are automatically created as 
    a result of adding server sockets or creating a child
    process, and returned to you as a part of an event.
    for subclasses, consult chan_* classes in ioman_unix
    and ioman_win modules.
    """
    def __init__(self, iom):
        """
        iom : an ioman_base object you obtained by 
              ioman.mk_ioman()
        """
        self.iom = iom

class ioman:
    """
    the base class representing an io manager. 
    """
    def create_pipe_regular_from_child_to_parent__(self):
        raise should_be_implemented_in_subclass()
    def create_pipe_sock_pair_from_child_to_parent__(self):
        raise should_be_implemented_in_subclass()
    def create_pipe_pty_from_child_to_parent__(self):
        raise should_be_implemented_in_subclass()
    def create_pipe_regular_from_parent_to_child__(self):
        raise should_be_implemented_in_subclass()
    def create_pipe_sock_pair_from_parent_to_child__(self):
        raise should_be_implemented_in_subclass()
    def create_pipe_pty_from_parent_to_child__(self):
        raise should_be_implemented_in_subclass()
        
    def create_pipe_from_child_to_parent__(self, kind):
        if kind is None or kind == pipe_type.REGULAR:
            return self.create_pipe_regular_from_child_to_parent__()
        elif kind == pipe_type.SOCK_PAIR:
            return self.create_pipe_sock_pair_from_child_to_parent__()
        elif kind == pipe_type.PTY:
            return self.create_pipe_pty_from_child_to_parent__()
        else:
            raise ioman_ex_bug()

    def create_pipe_from_parent_to_child__(self, kind):
        if kind is None or kind == pipe_type.REGULAR:
            return self.create_pipe_regular_from_parent_to_child__()
        elif kind == pipe_type.SOCK_PAIR:
            return self.create_pipe_sock_pair_from_parent_to_child__()
        elif kind == pipe_type.PTY:
            return self.create_pipe_pty_from_parent_to_child__()
        else:
            raise ioman_ex_bug()

    def register_std(self):
        """
        register stdin, stdout, stderr of this process for IO
        RETURN:
        return a triple of channel objects (rchan, wchan, wchan)
        """
        # should return ch_stdin,ch_stdout,ch_stderr
        raise should_be_implemented_in_subclass()

    def create_server_socket(self, addr_family, sock_type, qlen, addr):
        """
        create a server socket and register it for IO.
        further calls to next_event() method will watch the
        created socket, so if a client connects to it you
        may receive an event.

        addr_family : address family (socket.AF_INET, socket.AF_UNIX, etc.)
        sock_type : socket type (socket.SOCK_STREAM, socket.SOCK_DGRAM, etc.)
        qlen : queue length given to listen, as in listen(qlen)
        addr : address given to bind, as in bind(addr)
        RETURN:
        an instance chan_{unix,win}_server_socket class.
        """
        # should return chan
        raise should_be_implemented_in_subclass()
        
    def add_socket(self, so):
        """
        register a socket (unix socket or windows socket) for IO.
        further calls to next_event() method will watch the
        socket, so if a data is received from the socket you
        may receive an event.
        RETURN:
        an instance of chan_{unix,win}_socket class
        """
        raise should_be_implemented_in_subclass()

    def create_process(self, cmd, env, cwds, pipes, rlimit):
        """
        create a new process and register it for termination detection.
        cmd : a list describing a command line (e.g., [ "ls", "-l" ])
        env : a dictionary object specifying environment variables you
              want to add to child's environment (e.g., { "x" : "10" }),
              or None, if you do not have to add any
        cwds : a list of directories the child tries to change its
               working directory into. whichever it succeeded is used
               as the working directory. if cwds is None or if none 
               succeeds, then it inherits the parent's current working
               directory.
        pipes : specifies how the child and the parent is connected via
               pipes. a typical example is 
               [ (None, "w", 0), (None, "r", 1), (None, "r", 2) ].
               precisely, it is a list of triples (kind,r_or_w,fd) where
                 kind : None or one of pipe_type's constants specifying
                        what medium should be used for this pipe.
                        (pipe_type.REGULAR meaning a regular unix/windows pipe, 
                         pipe_type.SOCK_PAIR meaning a socket, or
                         pipe_type.PTY meatning pseudo tty). 
                         currently only REGULAR is tested.
                 r_or_w : "r" or "w", indicating whether it is "r"ead or
                        "w"ritten by the parent (caller) process.
                 fd : the file descriptor number the child process wants
                      to see this pipe as. for example,
                      (None, "w", 0) says the parent wants to write to
                      a channel and the child receives it from file 
                      descriptor 0.  (None, "r", 1) says the child writes
                      to file descriptor 1 and the parent reads it from
                      a channel.  On windows, only 0, 1, and 2 are allowed,
                      each meaning stdin, stdout, and stderr.

        pipes are created according to redirect.
        RETURN:
        a quadruple pid,r_chans,w_chans,err
        
        pid : process id (or None if failed)
        r_chans : list of read channels corresponding to those marked with "r"
                  in pipes
        w_chans : list of write channels corresponding to those marked with "w"
                  in pipes
        err     : None or exception object
        """
        # should return chan_child_proc,chans_to_read,chans_to_write
        raise should_be_implemented_in_subclass()

    def next_event(self, timeout): # to : timeout
        """
        wait at most timeout seconds for the next event to happen.
        RETURN: 
        an event object
        """
        # should return event_object
        raise should_be_implemented_in_subclass()
        
